#include <algorithm>
#include <assert.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <iterator>
#include <random>
#include <ratio>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <thread>

#include "check_sum.h"
#include "hex_dump.hpp"
#include <CppLinuxSerial/SerialPort.hpp>
using namespace mn::CppLinuxSerial;

namespace {

template <class Iter>
void fill_with_random_int_values(Iter start, Iter end, int min, int max) {
  static std::random_device rd;  // you only need to initialize it once
  static std::mt19937 mte(rd()); // this is a relative big object to create
  std::uniform_int_distribution<int> dist(min, max);
  std::generate(start, end, [&]() { return dist(mte); });
}

} // namespace

static std::atomic_bool run_flag{true};


int main(int argc, char *argv[]) {
  SerialPort serialPort("/dev/ttyUSB0", BaudRate::B_115200, NumDataBits::EIGHT, Parity::NONE, NumStopBits::ONE);
  serialPort.SetTimeout(-1);
  serialPort.Open();

  while (run_flag.load()) {
    std::vector<uint32_t> lens(1000);
    fill_with_random_int_values(lens.begin(), lens.end(), 1, 512);

    for (uint32_t len : lens) {
      uint32_t pl_len = len + sizeof(uint32_t);
      std::vector<uint8_t> pl(len), frame;
      frame.reserve(len + sizeof(uint32_t) * 2);

      fill_with_random_int_values(pl.begin(), pl.end(), 0, 255);
      uint32_t checksum = make_checksum_32(pl.data(), len);

      uint8_t head[sizeof(uint32_t)], end[sizeof(uint32_t)];
      memcpy(head, &pl_len, sizeof pl_len);
      memcpy(end, &checksum, sizeof checksum);

      std::copy(std::begin(head), std::end(head), std::back_inserter(frame));
      std::copy(pl.begin(), pl.end(), std::back_inserter(frame));
      std::copy(std::begin(end), std::end(end), std::back_inserter(frame));

      std::string frame_str = can::utils::bin2hex(frame.data(), frame.size());
      std::cout << frame_str << std::endl;
      auto tx_num = serialPort.Write(frame.data(), frame.size());
      if (run_flag.load()) {
        assert(tx_num == frame.size());
      }
      // std::this_thread::sleep_for(std::chrono::microseconds(300));

      if (false == run_flag.load())
        break;
    }
  }

  serialPort.Close();
  return 0;
}
